package online.inote.common.core;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;

import org.apache.ibatis.javassist.ClassClassPath;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute;
import org.apache.ibatis.javassist.bytecode.ClassFile;
import org.apache.ibatis.javassist.bytecode.ConstPool;
import org.apache.ibatis.javassist.bytecode.annotation.Annotation;
import org.apache.ibatis.javassist.bytecode.annotation.StringMemberValue;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.CollectionUtils;

import online.inote.common.annotation.GenerateMapper;
import online.inote.common.base.BaseMapper;
import online.inote.common.base.BaseService;
import online.inote.common.utils.ClassUtils;
import online.inote.common.utils.StringUtils;
import tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration;
import tk.mybatis.mapper.autoconfigure.MapperProperties;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import tk.mybatis.spring.mapper.MapperFactoryBean;

/**
 * <p>
 * Desc: 动态生成 Mapper Configuration
 * </p>
 *
 * @author Sui
 * @created: 2017年12月10日 上午11:25:05
 * @version 1.0
 */
@Configuration
@EnableConfigurationProperties({ MapperProperties.class, GenerateMapperProperties.class })
@AutoConfigureAfter(MapperAutoConfiguration.class)
public class DynamicGenerateMapperConfiguration {

	@Autowired
	private GenerateMapperProperties properties;
	private DefaultListableBeanFactory beanFactory;
	private MapperHelper mapperHelper = new MapperHelper();

	@Autowired
	public DynamicGenerateMapperConfiguration(ApplicationContext applicationContext, MapperProperties properties,
			List<SqlSessionFactory> sqlSessionFactoryList) {
		ConfigurableApplicationContext context = (ConfigurableApplicationContext) applicationContext;
		beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();

		mapperHelper.setConfig(properties);

		for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
			mapperHelper.processConfiguration(sqlSessionFactory.getConfiguration());
		}
	}

	/**
	 * <p>
	 * Desc: 动态生成Mapper启动
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午10:18:54
	 * @throws Exception
	 */
	@PostConstruct
	public void dynamicGenerateMapperConfiguration() throws Exception {
		Set<Class<?>> genericSet = scanGenerateMapperEntity();

		if (!CollectionUtils.isEmpty(genericSet)) {
			Set<Class<?>> existMapperModelSet = scanExistModel(BaseMapper.class, true);
			Set<Class<?>> existServiceModelSet = scanExistModel(BaseService.class, false);
			
			genericSet.forEach(generic -> {
				if (!existMapperModelSet.contains(generic)) {
					generateMapper(generic, existServiceModelSet);
				}
			});
		}
	}

	/**
	 * <p>
	 * Desc: 扫描已存在Mapper的Model
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午11:02:13
	 * @return
	 */
	public Set<Class<?>> scanExistModel(Class<?> superClass, boolean isInterface) {
		if (StringUtils.isBlank(properties.getMapperPackage())) {
			throw new RuntimeException("mapperPackage不能为空");
		}

		Set<Class<?>> classSet = ClassUtils.scanClassBySuperClass(superClass, properties.getMapperPackage().split(","));
		classSet.remove(superClass);

		Set<Class<?>> existMapperModelSet = new HashSet<>();
		if (!CollectionUtils.isEmpty(classSet)) {
			classSet.forEach(clazz -> {
				Class<?> generic = null;
				
				if (isInterface) {
					ClassUtils.getGenericInterface(clazz);
				} else {
					ClassUtils.getGenericClass(clazz);
				}
				
				existMapperModelSet.add(generic);
			});
		}

		return existMapperModelSet;
	}

	/**
	 * <p>
	 * Desc: 扫描需要生成Mapper的Model
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午10:38:59
	 * @param filter
	 * @return
	 */
	public Set<Class<?>> scanGenerateMapperEntity() {
		if (StringUtils.isBlank(properties.getModelPackage()))
			throw new RuntimeException("modelPackage不能为空");

		return ClassUtils.scanClassByAnnotation(GenerateMapper.class, properties.getMapperPackage().split(","));
	}

	/**
	 * <p>
	 * Desc: 生成Mapper
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午10:19:24
	 * @param generic
	 * @param existServiceModelSet 
	 * @param anno
	 */
	public void generateMapper(Class<?> generic, Set<Class<?>> existServiceModelSet) {
		GenerateMapper anno = generic.getAnnotation(GenerateMapper.class);
		
		String sqlSessionFactoryBeanName = anno.sqlSessionFactoryBeanName(); // 数据源beanName
		String name = generateName(generic.getSimpleName(), true); // 获取生成的类文件ClassName
		String packagePath = anno.mapperPath(); // 生成包路径

		if (StringUtils.isBlank(packagePath)) {
			packagePath = generatePackage(generic) + ".mapper";
		}

		try {
			String genString = "<L" + generic.getName().replace(".", "/") + ";>";
			String beseString = "L" + BaseMapper.class.getName().replace(".", "/");

			ClassPool pool = ClassPool.getDefault();
			pool.insertClassPath(new ClassClassPath(this.getClass()));

			CtClass superIn = pool.get(BaseMapper.class.getName());
			CtClass ct = pool.makeInterface(packagePath + "." + StringUtils.upperCase(name));
			ct.setGenericSignature("Ljava/lang/Object;" + beseString + genString + ";");
			ct.setInterfaces(new CtClass[] { superIn });

			Class<?> clazz = ct.toClass();

			// 注册bean
			BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(clazz).getRawBeanDefinition();
			definition.setBeanClassName(MapperFactoryBean.class.getName());
			definition.getPropertyValues().add("mapperHelper", mapperHelper);
			definition.getPropertyValues().add("mapperInterface", clazz);

			SqlSessionFactory factory = (SqlSessionFactory) beanFactory.getBean(sqlSessionFactoryBeanName);

			if (factory == null) {
				throw new RuntimeException(
						"获取SqlSessionFactory失败, sqlSessionFactoryBeanName:[" + sqlSessionFactoryBeanName + "]");
			}

			definition.getPropertyValues().add("sqlSessionFactory", factory);
			beanFactory.registerBeanDefinition(name, definition);
		} catch (Exception e) {
			throw new RuntimeException("动态生成接口失败", e);
		}
		
		if (anno.isGenerateService() && !existServiceModelSet.contains(generic)) {
			generateService(generic, anno);
		}
	}

	/**
	 * <p>
	 * Desc: 生成Service
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午10:19:39
	 * @param generic
	 * @param anno
	 */
	public void generateService(Class<?> generic, GenerateMapper anno) {
		String name = generateName(generic.getSimpleName(), false);
		String packagePath = anno.mapperPath(); // 生成包路径
		String transactionManager = anno.transactionManager();

		if (StringUtils.isBlank(packagePath)) {
			packagePath = generatePackage(generic) + ".service";
		}

		try {
			String genString = "<L" + generic.getName().replace(".", "/") + ";>";
			String beseString = "L" + BaseService.class.getName().replace(".", "/");
			ClassPool pool = ClassPool.getDefault();
			pool.insertClassPath(new ClassClassPath(this.getClass()));
			CtClass superIn = pool.get(BaseService.class.getName());
			CtClass ct = pool.makeClass(packagePath + "." + StringUtils.upperCase(name) + "GenerateService");
			ct.setGenericSignature(beseString + genString + ";");
			ct.setSuperclass(superIn);

			// 是否增加事务注解
			if (StringUtils.isBlank(transactionManager)) {
				ClassFile classFile = ct.getClassFile();
				ConstPool constPool = classFile.getConstPool();

				AnnotationsAttribute bodyAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
				Annotation bodyAnnot = new Annotation("org.springframework.transaction.annotation.Transactional",
						constPool);
				bodyAnnot.addMemberValue("transactionManager", new StringMemberValue("HelloWoldService", constPool));
				bodyAttr.addAnnotation(bodyAnnot);

				classFile.addAttribute(bodyAttr);
			}

			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(ct.toClass());
			beanFactory.registerBeanDefinition(name, beanDefinitionBuilder.getBeanDefinition());
		} catch (Exception e) {
			throw new RuntimeException("动态生成Service失败", e);
		}
	}

	/**
	 * <p>
	 * Desc: 生成BeanName
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午10:22:12
	 * @param genericName 泛型BeanName
	 * @param isGenerateMapper
	 * @return
	 */
	public String generateName(String genericName, boolean isGenerateMapper) {

		genericName = genericName.replace("Entity", "");
		genericName = genericName.replace("Model", "");

		return "Generate" + genericName + (isGenerateMapper ? "Mapper" : "Service");
	}

	/**
	 * <p>
	 * Desc: 默认生成路径和实体路径的上级路径
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月10日 下午10:22:38
	 * @param clazz
	 * @return
	 */
	public static String generatePackage(Class<?> clazz) {
		String packagePath = clazz.getName();

		if (packagePath.contains(".")) {
			packagePath = packagePath.substring(0, packagePath.lastIndexOf("."));
		}

		if (packagePath.contains(".")) {
			packagePath = packagePath.substring(0, packagePath.lastIndexOf("."));
		}

		return packagePath;
	}
}
